/**
  ******************************************************************************
  * @file    drv_modbus_port.c
  * @author  Forester
  * @brief   For porting and adapting files, users of the library should 
	*          correctly fill in the functions provided in this file.
  ******************************************************************************
  * @attention
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "drv_modbus.h"
#include "stm32f1xx_hal.h"
#include <stdio.h>
#include "stm32f1xx.h"

#include "freertos.h"
#include "queue.h"

/* Private variable ---------------------------------------------------------*/
static TIM_HandleTypeDef htim4;
static QueueHandle_t eventQueue = NULL;

/**
  * @brief Init the serial port that modbus need.
  * 
	* @note need open interrupt for RXNE, and priority must higher than idle check timer.
  */
bool ModbusRTU_InitUsart(const ModbusRTU_InitTypedef * initTypedef)
{
	return true;
}

bool ModbusRTU_ReadByte( uint8_t *byte )
{
	*byte = USART1->DR & (( uint16_t ) 0x00FF);
	return true;
}

/**
  * @brief Init the serial port that modbus need.
  *
  * @note 1.Don't start timer, just init it and will be control by thoese function :
  *       ModbusRTU_EnableTimer, ModbusRTU_DisableTimer.
  *       2.Timer must open interrupt service, and priority must lower than USART
  */
bool ModbusRTU_InitIdleCheckTimer(uint32_t idleUs)
{
	TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM4_Init 1 */

  /* USER CODE END TIM4_Init 1 */
  htim4.Instance = TIM4;
  htim4.Init.Prescaler = 3599;
  htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim4.Init.Period = idleUs - 1;
  htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
  {
    return false;
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
  {
		return false;
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
  {
    return false;
  }
	return true;
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
  if(tim_baseHandle->Instance==TIM4)
  {
    __HAL_RCC_TIM4_CLK_ENABLE();
		
    /* TIM4 interrupt Init */
    HAL_NVIC_SetPriority(TIM4_IRQn, 7, 7);
    HAL_NVIC_EnableIRQ(TIM4_IRQn);

  }
}

#if ( SYSTEM_SUPPORT == 1 )
bool ModbusRTU_OSSourceInit( void )
{
	eventQueue = xQueueCreate( 5, sizeof( ModbusRTU_Event ));
	if(!eventQueue)
	{
		return false;
	}
	return true;
}
#endif


/**
  * @brief Start idle check timer.
  *        Interrupt service will change modbus state to IDLE and handle receive data.
  *
	* @note You just need implement two step in this function :
  *       1. step one: reset timer's cnt 0 if you init timer with count up mode otherwise
  *          reset timer's cnt as ARR register value.
  *       2. step two: start timer and ensure that interrupts are enabled.
  */
void ModbusRTU_EnableTimer( void )
{
	/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
	__HAL_TIM_SET_COUNTER(&htim4, 0);
	HAL_TIM_Base_Start_IT(&htim4);
}

/**
  * @brief Stop idle check timer.
  * 
  */
void ModbusRTU_DisableTimer( void )
{
	HAL_TIM_Base_Stop_IT(&htim4); 
	/* Disable any pending timers. */
}

void TIM4_IRQHandler( void )
{
	if(__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE))
	{
		__HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);		
		ModbusRTU_DisableTimer();	
		
		// Send receive event.
		ModbusRTU_TimerExpiredHandler();
	}
}

void ModbusRTU_Enable_Interrupts( void )
{
	taskENABLE_INTERRUPTS();
}

void ModbusRTU_Disable_Interrupts( void )
{
	taskDISABLE_INTERRUPTS();
}

ModbusRTU_Event ModbusRTU_GetEvent( void )
{
	ModbusRTU_Event event;
	xQueueReceive( eventQueue, &event, portMAX_DELAY );
	return event;
}

/**
  * @brief Send to event loop.
  *
	* @note This function will be called within an interrupt context. When using 
  *       an operating system, be mindful to use the interrupt-safe versions of the system APIs. 
  */
bool ModbusRTU_SendEvent( ModbusRTU_Event event )
{
	return xQueueSendFromISR(eventQueue, &event, 0) == pdTRUE;
}


__weak void ModbusRTU_0x06_HandlerPort( uint8_t *buffer, uint8_t dataLen )
{
	printf("Receive 0x06 function code\r\n");
}
